<?php
class SocketServer
{
	public $ip = '127.0.0.1';
	public $port = 30001;

	protected $clients = array();
	private $_sock = null;

	private $_timers = array();

	public function onConnection($socket) {
		return new PacketStream($socket);
	}

	// create socket server
	function  __construct($ip, $port) {
		$this->ip = $ip;
		$this->port = $port;
		
		$this->_sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
		if(!$this->_sock)
			throw new Exception("socket_create(): " . socket_strerror(socket_last_error()));
		
		socket_set_option($this->_sock, SOL_SOCKET, SO_REUSEADDR, 1);

		if(socket_bind($this->_sock, $this->ip, $this->port) < 0)
			throw new Exception("socket_bind(): " . socket_strerror(socket_last_error()));

		if(!socket_listen($this->_sock))
			throw new Exception("socket_listen(): " . socket_strerror(socket_last_error()));
	}

	static public function ___sortTimersCb($t1, $t2) {
		if($t1['lastRunTime'] + $t1['interval'] > $t2['lastRunTime'] + $t2['interval'])
			return 1;
		return -1;
	}

	private function _sortTimers() {
		usort($this->_timers, 'SocketServer::___sortTimersCb');
	}

	private function _timersGetVacationTime() {
		if(!sizeof($this->_timers))
			return 30;

		$now = microtime(true);
		$wait = ($this->_timers[0]['lastRunTime'] + $this->_timers[0]['interval']) - $now;
		return $wait > 0 ? $wait : 0;
	}

	private function _timersRun() {
		$now = microtime(true);

		foreach($this->_timers as &$t) {
			if($t['lastRunTime'] + $t['interval'] <= $now) {
				call_user_func($t['callback']);
				$t['lastRunTime'] += $t['interval'];
			}
		}
		unset($t);
		
		$this->_sortTimers();
	}

	public function registerTimer($interval, $callback, $isPeriodic = true) {
		$this->_timers[] = array(
			'interval' => $interval,
			'lastRunTime' => microtime(true),
			'callback' => $callback
		);

		$this->_sortTimers();
	}

	public function shutdown() {

	}

	public function start()	{
		$read = array();
		$write = array();
		$except = array();

		Log::notice('Server started');

		do {
			$this->_timersRun();
			
			foreach($this->clients as $c) {
				if($c->checkTimeout())
					$this->_unregisterConnection($c);
			}

			/**
			 * @todo ахтунг, переписать при первой возможности
			 */

			$toRead = array();
			foreach($read as $fd) {
				if($fd == $this->_sock) {
					$sock = socket_accept($this->_sock);
					if($sock === false)
						throw new Exception('socket_accept(): ' . socket_strerror(socket_last_error($this->_sock)));

					Log::notice('Incoming connection');
					
					$connection = $this->onConnection($sock);
					if($connection) {
						Log::notice('Accepting');
						$this->_registerConnection($connection);
						$connection->_setServer($this);
						$connection->onAccept();
						Log::notice('Accepted');
					} else {
						Log::notice('Dropped');
						socket_close($sock);
					}
				} else {
					foreach($this->clients as $c) {
						if($c->_sock == $fd)
							$toRead[] = $c;
					}
				}
			}

			$toWrite = array();
			foreach($write as $fd) {
				foreach($this->clients as $c) {
					if($c->_sock == $fd)
						$toWrite[] = $c;
				}
			}

			foreach($toRead as $c) {
				$c->_recv();
				if($c->_isClosed())
					$this->_unregisterConnection($c);
			}

			foreach($toWrite as $c) {
				$c->_send();
				if($c->_isClosed())
					$this->_unregisterConnection($c);
			}
			
			$wait = $this->_timersGetVacationTime();
//			Log::notice('Cron vacation time: ' . $wait);
			$tsec = (int)$wait;
			$tusec = ($wait - $tsec) * 1000000;

			$read = array($this->_sock);
			$write = array();
			
			foreach($this->clients as $c) {
				$read[] = $c->_sock;
				if($c->_needRecv())
					$write[] = $c->_sock;
			}
			
		} while((socket_select($read, $write, $except, $tsec, $tusec) !== false));
	}

	private function _unregisterConnection(Connection $c) {
		foreach($this->clients as $i=>$cc) {
			if($cc == $c) {
				unset($this->clients[$i]);
				$this->clients = array_values($this->clients); // array_splice
			}
		}
		Log::notice('Closed');
	}

	private function _registerConnection(Connection $c) {
		$this->clients[] = $c;
	}
}